﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

namespace Tyshkovets.Dcpu.Core
{
    class Dcpu
    {
        private List<String> basicOperations = new List<String> { "SET", "ADD", "SUB", "MUL", "DIV", "MOD", "SHL", "SHR", "AND", "BOR", "XOR", "IFE", "IFN", "IFG", "IFB" };
        private List<String> conditionOperations = new List<String> {"IFE", "IFN", "IFG", "IFB"};
        private List<String> nonBasicOperations = new List<String> {"JSR"};
        private List<String> commonRegisters = new List<String> { "A", "B", "C", "X", "Y", "Z", "I", "J" };
        private List<String> specialRegisters = new List<String> { "POP", "PEEK", "PUSH", "SP", "PC", "O" };
        private List<String> reservedWords = new List<String> { "A", "B", "C", "X", "Y", "Z", "I", "J", "POP", "PEEK", "PUSH", "SP", "PC", "O" };
        private List<String> reservedKeywords = new List<String> { "SET", "ADD", "SUB", "MUL", "DIV", "MOD", "SHL", "SHR", "AND", "BOR", "XOR", "IFE", "IFN", "IFG", "IFB", "JSR", "DAT", "ORG" };

        private ulong cycles;

        private UInt16 GetValue(UInt16 code, Dictionary<UInt16, UInt16> memory, Dictionary<String, UInt16> registers, bool noChange)
        {
            // value is stored in the register. (A for 0x00, J for 0x07)
            if (code < 0x8)
            {
                return registers[commonRegisters[code]];
            }

            // value is stored in the [register] 
            if (code < 0x10)
            {
                return memory[registers[commonRegisters[code - 0x8]]];
            }

            // value is stored in the [register + next word]
            if (code < 0x18)
            {
                var nw = memory[registers["PC"]];
                registers["PC"] = (UInt16) ((registers["PC"] + 1) & 0xFFFF);
                cycles++;
                return memory[(UInt16)((nw + registers[commonRegisters[code - 0x10]]) & 0xFFFF)];
            }

            switch (code)
            {
                case 0x18: // POP
                    var stackPopValue = memory[registers["SP"]];
                    if (!noChange)
                    {
                        registers["SP"] = (UInt16)((registers["SP"] + 1) & 0xFFFF);
                    }
                    return stackPopValue;
                case 0x19: // PEEK
                    return memory[registers["SP"]];
                case 0x1a: // PUSH
                    var stackPushValue = memory[(UInt16) ((registers["SP"] - 1) & 0xFFFF)];
                    if (!noChange)
                    {
                        registers["SP"] = (UInt16)((registers["SP"] - 1) & 0xFFFF);
                    }
                    return stackPushValue;
                case 0x1b: // SP
                    return registers["SP"];
                case 0x1c: // PC
                    return registers["PC"];
                case 0x1d: // O
                    return registers["O"];
                case 0x1e: // [next word]
                    var nw = memory[registers["PC"]];
                    cycles++;
                    registers["PC"] = (UInt16) ((registers["PC"] + 1) & 0xFFFF);
                    return memory[nw];
                case 0x1f: // next word (literal)
                    var nwValue = memory[registers["PC"]];
                    cycles++;
                    registers["PC"] = (UInt16)((registers["PC"] + 1) & 0xFFFF);
                    return nwValue;
                default:
                    return (UInt16)(code - 0x20);
            }
        }

        private void SetValue(UInt16 code, UInt16 value, Dictionary<UInt16, UInt16> memory, Dictionary<String, UInt16> registers, Dictionary<String, UInt16> updatedRegisters)
        {
            if (code < 0x8)
            {
                updatedRegisters[commonRegisters[code]] = value;
                return;
            }

            if (code < 0x10)
            {
                memory[registers[commonRegisters[code - 0x8]]] = value;
                return;
            }

            if (code < 0x18)
            {
                var offset = memory[registers["PC"]];
                memory[(UInt16) ((offset + registers[commonRegisters[code - 0x10]]) & 0xFFFF)] = value;
                return;
            }

            switch (code)
            {
                case 0x18: // POP
                    memory[registers["SP"]] = value;
                    return;
                case 0x19: // PEEK
                    memory[registers["SP"]] = value;
                    return;
                case 0x1a: // PUSH
                    memory[updatedRegisters["SP"]] = value;
                    return;
                case 0x1b:
                    updatedRegisters["SP"] = value;
                    return;
                case 0x1c:
                    updatedRegisters["PC"] = value;
                    return;
                case 0x1d:
                    updatedRegisters["O"] = value;
                    return;
                case 0x1e:
                    memory[memory[registers["PC"]]] = value;
                    return;
            }

            return;
        }

        private void Skip(Dictionary<UInt16, UInt16> memory, Dictionary<String, UInt16> registers)
        {
            var currentStep = memory[registers["PC"]];
            var opcode = (UInt16) (currentStep & 0xF);
            // TODO : Check that this shift is unsigned
            var operandA = (UInt16) ((currentStep >> (UInt16)4) & 0x3F);
            var operandB = (UInt16) ((currentStep >> (UInt16)10) & 0x3f);
            var localCycles = this.cycles;
            registers["PC"] = (UInt16) ((registers["PC"] + 1) & 0xFFFF);
            GetValue(operandA, memory, registers, true);
            GetValue(operandB, memory, registers, true);
            cycles = localCycles;
        }

        private UInt16 Step(Dictionary<UInt16, UInt16> memory, Dictionary<String, UInt16> registers)
        {
            var currentStep = memory[registers["PC"]];
            var opcode = (UInt16)(currentStep & 0xF);
            var operandA = (UInt16)((currentStep >> (UInt16)4) & 0x3F);
            var operandB = (UInt16)((currentStep >> (UInt16)10) & 0x3f);
            registers["PC"] = (UInt16)((registers["PC"] + 1) & 0xFFFF);
            var registersCopy = registers.ToDictionary(r => r.Key, r => r.Value);
            cycles = 0;

            switch (opcode)
            {
                case 0x00:
                    switch(operandA)
                    {
                        case 0x1 : // JSR
                            {
                                var aValue = GetValue(operandB, memory, registers, false);
                                registers["SP"] = DecrementValue(registers["SP"]);
                                memory[registers["SP"]] = registers["PC"];
                                registers["PC"] = aValue;
                                return (UInt16)(cycles + 2);
                            }
                        default:
                            {
                                return 0;
                            }
                    }
                    break;
                case 0x01:
                    GetValue(operandA, memory, registers, false);
                    var bValue = GetValue(operandB, memory, registers, false);
                    SetValue(operandA, operandB, memory, registersCopy, registers);
                    return (UInt16) (cycles + 1);
                case 0x02:
                    var v = (UInt32) (GetValue(operandA, memory, registers, false) + GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16) (v & 0xffff), memory, registersCopy, registers);
                    registers["O"] = (UInt16)((v >> 16) & 0xffff);
                    return (UInt16)(cycles + 2);
                case 0x03:
                    var subValue = (UInt32) (GetValue(operandA, memory, registers, false) - GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16) ( subValue & 0xffff), memory, registersCopy, registers);
                    registers["O"] =  (UInt16)((subValue >> 16) & 0xffff);
                    return (UInt16)(cycles + 2);
                case 0x04:
                    var mulValue = (UInt32)(GetValue(operandA, memory, registers, false) * GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16)(mulValue & 0xffff), memory, registersCopy, registers);
                    registers["O"] = (UInt16)((mulValue >> 16) & 0xffff);
                    return (UInt16)(cycles + 2);
                case 0x05:
                    var aDivValue = GetValue(operandA, memory, registers, false);
                    var bDivValue = GetValue(operandB, memory, registers, false);
                    if (bDivValue == 0)
                    {
                        SetValue(operandA, 0, memory, registersCopy, registers);
                        registers["O"] = 0;
                    }
                    else
                    {
                        var res = (UInt32)( aDivValue / bDivValue );
                        SetValue(operandA, (UInt16)(res & 0xffff), memory, registersCopy, registers);
                        registers["O"] = (UInt16)((res * 0x10000) & 0xffff);
                    }
                    return (UInt16)(cycles + 3);
                case 0x06:
                    var aModValue = GetValue(operandA, memory, registers, false);
                    var bModValue = GetValue(operandB, memory, registers, false);
                    SetValue(operandA, (bModValue == 0) ? (UInt16)0 : (UInt16)(aModValue % bModValue), memory, registersCopy, registers);
                    return (UInt16)(cycles + 3);
                case 0x07:
                    var leftShiftValue = (UInt32)(GetValue(operandA, memory, registers, false) << GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16)(leftShiftValue & 0xffff), memory, registersCopy, registers);
                    registers["O"] = (UInt16)((leftShiftValue >> 16) & 0xffff);
                    return (UInt16)(cycles + 2);
                case 0x08:
                    var rightShiftAValue = GetValue(operandA, memory, registers, false);
                    var rightShiftBValue = GetValue(operandB, memory, registers, false);
                    SetValue(operandA, (UInt16) ((rightShiftAValue >> rightShiftBValue) & 0xffff), memory, registersCopy, registers);
                    registers["O"] = (UInt16)(((rightShiftAValue << (UInt16)16) >> rightShiftBValue) & 0xffff);
                    return (UInt16)(cycles + 2);
                case 0x09:
                    var andValue = (UInt32)(GetValue(operandA, memory, registers, false) & GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16)(andValue & 0xffff), memory, registersCopy, registers);
                    return (UInt16)(cycles + 1);
                case 0xa:
                    var orValue = (UInt32)(GetValue(operandA, memory, registers, false) | GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16)(orValue & 0xffff), memory, registersCopy, registers);
                    return (UInt16)(cycles + 1);
                case 0xb:
                    var norValue = (UInt32)(GetValue(operandA, memory, registers, false) ^ GetValue(operandB, memory, registers, false));
                    SetValue(operandA, (UInt16)(norValue & 0xffff), memory, registersCopy, registers);
                    return (UInt16)(cycles + 1);
                case 0xc:
                    var cValueA = GetValue(operandA, memory, registers, false);
                    var cValueB = GetValue(operandB, memory, registers, false);
                    if (cValueA != cValueB)
                    {
                        Skip(memory, registers);
                        return (UInt16) (cycles + 3);
                    }
                    return (UInt16) (cycles + 2);
                case 0xd:
                    var dValueA = GetValue(operandA, memory, registers, false);
                    var dValueB = GetValue(operandB, memory, registers, false);
                    if (dValueA == dValueB)
                    {
                        Skip(memory, registers);
                        return (UInt16)(cycles + 3);
                    }
                    return (UInt16)(cycles + 2);
                case 0xe:
                    var eValueA = GetValue(operandA, memory, registers, false);
                    var eValueB = GetValue(operandB, memory, registers, false);
                    if (eValueA <= eValueB)
                    {
                        Skip(memory, registers);
                        return (UInt16)(cycles + 3);
                    }
                    return (UInt16)(cycles + 2);
                case 0xf:
                    var fValueA = GetValue(operandA, memory, registers, false);
                    var fValueB = GetValue(operandB, memory, registers, false);
                    if ((fValueA & fValueB) == 0)
                    {
                        Skip(memory, registers);
                        return (UInt16)(cycles + 3);
                    }
                    return (UInt16)(cycles + 2);
            }

            return 0;
        }

        private UInt16 DecrementValue(UInt16 value)
        {
            return (UInt16) ((value - 1) & 0xFFFF);
        }

        private class ResObject
        {
            public bool BoolField { get; set; }
            public int End { get; set; }
            public int? Literal { get; set; }
            public int? Register { get; set; }
            public String Label { get; set; }
            public bool Incomplete { get; set; }

            public String Operation { get; set; }
            public List<ResObject> Operands { get; set; }
        }

        private ResObject ParseExpression(int index, int offset, String expression, int position, Dictionary<String, UInt16> labels)
        {
            var res = new ResObject {BoolField = false};

            while (position < expression.Length)
            {
                String operation = String.Empty;
               
                if (res != null && res.BoolField == true)
                {
                    while (position < expression.Length && expression[position] == ' ')
                    {
                        position++;
                    }

                    if (position >= expression.Length)
                    {
                        break;
                    }

                    if (expression[position] == ')')
                    {
                        res.End = position + 1;
                        return res;
                    }

                    if (expression[position] == '+' || expression[position] == '-' || expression[position] == '*')
                    {
                        operation = String.Format("{0}", expression[position]);
                    }
                    else
                    {
                        throw new Exception("Only three operations are accepted in expressions");
                    }

                    position++;
                }

                while (position < expression.Length && expression[position] == ' ')
                {
                    position++;
                }

                if (position >= expression.Length)
                {
                    throw new Exception("Value expected before end of line");
                }

                var unary = (char) 0;
                if (expression[position] == '-' && expression[position] == '+')
                {
                    unary = expression[position];
                    position++;
                }

                while (position < expression.Length && expression[position] == ' ') 
                {
                    position++;
                }

                if (position >= expression.Length)
                {
                    throw new Exception("Value expected before end of line");
                }


                var value = new ResObject();
                #region [Started with (]
                if (expression[position] == '(')
                {
                    value = ParseExpression(index, offset, expression, position + 1, labels);
                    if (value != null && value.BoolField == true)
                    {
                        position = value.End;
                    }
                    else
                    {
                        return new ResObject {BoolField = false};
                    }
                }
                else
                {
                    var operand = String.Empty;
                    while (position < expression.Length && "+-*/()[],".IndexOf(expression[position]) == -1)
                    {
                        operand += expression[position];
                        position++;
                    }
                    if (Regex.IsMatch(operand, "/^[0-9]+$/g"))
                    {
                        value.Literal = Convert.ToInt32(operand, 10);
                    } else 
                    if (Regex.IsMatch(operand, "/^0x[0-9a-fA-F]+$/g"))
                    {
                        value.Literal = Convert.ToInt32(operand, 16);
                    }
                    
                    else
                    if (commonRegisters.IndexOf(operand.ToUpper()) >= -1)
                    {
                        value.Register = commonRegisters.IndexOf(operand.ToUpper());
                    }
                    else
                    if (specialRegisters.IndexOf(operand.ToUpper()) >= -1)
                    {
                        throw new Exception("You can not use special register in expression");
                    } else
                    if (labels != null)
                    {
                         if (labels.ContainsKey(operand.ToLower()))
                         {
                             value.Label = operand.ToLower();
                             value.Literal = labels[value.Label];
                         }
                         else
                         {
                             throw new Exception("Invalid value");
                         }
                    }
                    else
                    if (Regex.IsMatch(operand, "/^[a-zA-Z_.][a-zA-Z_.0-9]*$/"))
                    {
                        value.Label = operand.ToLower();
                        value.Incomplete = true;
                    }
                    else
                    {
                        throw new Exception("Invalid value");
                    }
                }
                #endregion

                if (unary == '-')
                {
                    if (value.Literal.HasValue)
                    {
                        value.Literal = -value.Literal;
                    }
                    else
                    {
                        value = new ResObject() {Operation = "U-", Operands = new List<ResObject>{value}};
                    }
                }

                if (res != null)
                {
                    if (operation == "*" && (res.Operation == "+" || res.Operation == "-"))
                    {
                        res.Operands[1] = (new ResObject {Operation = operation, Operands = new List<ResObject> {res.Operands[1], value}});
                    }
                    else if (operation == "-")
                    {
                        res = new ResObject
                                  {
                                      Operation = operation,
                                      Operands =
                                          new List<ResObject>
                                              {
                                                  res,
                                                  new ResObject {Operation = "U-", Operands = new List<ResObject> {value}}
                                              }
                                  };
                    }
                    else
                    {
                        res = new ResObject {Operation = operation, Operands = new List<ResObject> {res, value}};
                    }
                }
                else
                {
                    res = value;
                }
            }

            if (res == null)
            {
                throw new Exception("Value expected");
            }

            res.End = position;
            return res;
        }

        private ResObject SimplifyExpression(ResObject expression)
        {
            if (expression == null)
            {
                return expression;
            }

            var incomplete = false;
            for (int i = 0; i< expression.Operands.Count; i++)
            {
                expression.Operands[i] = SimplifyExpression(expression.Operands[i]);
                if (expression.Operands[i].Incomplete)
                {
                    incomplete = true;
                }
            }
            switch (expression.Operation)
            {
                case "U-":
                    if (expression.Operands[0].Literal.HasValue)
                    {
                        return new ResObject {Literal = -expression.Operands[0].Literal, Incomplete = incomplete};
                    }
                    return expression;
                case "-":
                    if ((expression.Operands[0].Literal.HasValue || expression.Operands[0].Incomplete) && (expression.Operands[1].Literal.HasValue || expression.Operands[1].Incomplete))
                    {
                        return new ResObject {Literal = expression.Operands[0].Literal - expression.Operands[1].Literal, Incomplete = incomplete};
                    }
                    return expression;
                case "+":
                    if ((expression.Operands[0].Literal.HasValue || expression.Operands[0].Incomplete) && (expression.Operands[1].Literal.HasValue || expression.Operands[1].Incomplete))
                    {
                        return new ResObject { Literal = expression.Operands[0].Literal + expression.Operands[1].Literal, Incomplete = incomplete };
                    }
                    return expression;
                case "*":
                    if ((expression.Operands[0].Literal.HasValue || expression.Operands[0].Incomplete) && (expression.Operands[1].Literal.HasValue || expression.Operands[1].Incomplete))
                    {
                        return new ResObject { Literal = expression.Operands[0].Literal * expression.Operands[1].Literal, Incomplete = incomplete };
                    }
                    return expression;
            }

            return expression;
        }

        public class DecodeValueResult
        {
            public int Size { get; set; }
            public int MaxSize { get; set; }
            public int Code { get; set; }
            public bool Complete { get; set; }
            public bool Pointer { get; set; }
            public int? NextWord { get; set; }
            public int? MinSize { get; set; }
            public bool Literal { get; set; }
        }

        public DecodeValueResult DecodeValue(int index, int offset, String val, Dictionary<String, UInt16> labels)
        {
            var info = new DecodeValueResult();
            if (val.Length == 0)
            {
                throw new Exception("Value must not be empty");
            }

            var pointer = false;
            if (val[0] == '[')
            {
                pointer = true;
                if (val.Length < 2 || val[val.Length - 1] != ']')
                {
                    throw new Exception("Expected ] before end of value");
                    
                }
                val = val.Substring(1, val.Length - 2).Trim();
            }
            info.Pointer = pointer;

            info.Size = 0;
            info.MaxSize = 0;
            info.Complete = true;

            var reg = commonRegisters.IndexOf(val.ToUpper());
            if (reg >= 0)
            {
                info.Code = (pointer ? 0x08 : 0x00) + reg;
                return info;
            }
            // POP, PEEK, PUSH, SP, PC, O
            var add = specialRegisters.IndexOf(val.ToUpper());
            if (add >= 0)
            {
                info.Code = 0x18 + add;
                if (pointer)
                {
                    throw new Exception("You can not use pointer to ");
                }
                return info;
            }

            var value = ParseExpression(index, offset, val, 0, labels);
            if (value == null)
            {
                throw new Exception();
            }

            value = SimplifyExpression(value);

            reg = -1;
            if (pointer && value.Operation == "+")
            {
                if (value.Operands[0].Register.HasValue && (value.Operands[1].Literal.HasValue || !String.IsNullOrEmpty(value.Operands[1].Label)))
                {
                    reg = value.Operands[0].Register.Value;
                    value = value.Operands[1];
                }
                else if (value.Operands[1].Register.HasValue && (value.Operands[0].Literal.HasValue || !String.IsNullOrEmpty(value.Operands[0].Label)))
                {
                    reg = value.Operands[1].Register.Value;
                    value = value.Operands[0];
                }
                else
                {
                    throw new Exception("Only sum of register and literal as value is allowed");
                }
            }

            if (!value.Literal.HasValue && String.IsNullOrEmpty(value.Label))
            {
                throw new Exception("Expression is too complex");
            }

            if (value.Literal.HasValue && (value.Literal < 0 || value.Literal > 0xffff))
            {
                value.Literal = value.Literal & 0xFFFF;
            }

            if (!pointer && String.IsNullOrEmpty(value.Label) && value.Literal.HasValue && value.Literal < 32)
            {
                info.Code = 0x20 + value.Literal.Value;
            }
            else
            {
                info.Code = (reg > -1 ? (0x10 + reg) : (pointer ? 0x1e : 0x1f));
                info.NextWord = value.Literal;
                info.Size = 1;
                info.MaxSize = 1;
                if (!value.Literal.HasValue)
                {
                    info.MinSize = 0;
                    info.Complete = false;
                }
            }

            info.Literal = true;
            return info;
        }

        public class CompileLineResult
        {
            public int MaxSize { get; set; }
            public int Size { get; set; }

            public String Direcrive { get; set;}
            public String Label { get; set; }
            public String Op { get; set; }
            public List<String> dump = new List<string>();
            public int? Org { get; set; }
        }

        public class Macro
        {
            public List<String> Lines { get; set; }
            public List<String> Params { get; set; }
        }

        public CompileLineResult CompileLine(int index, int offset, String line, Dictionary<String, UInt16> labels, Dictionary<String, Macro> macros, Dictionary<String, String> subst)
        {
            var info = new CompileLineResult {MaxSize = 0, Size = 0, dump = new List<string>()};
            var in_string = false;
            for (var i =0; i< line.Length; i++)
            {
                if (in_string && line[i] == '\\' && i < line.Length - 1)
                {
                    i++;
                }
                else if (line[i] == '"')
                {
                    in_string = !in_string;
                }
                if (line[i] == ';' && !in_string)
                {
                    line = line.Substring(0, i);
                    break;
                }
            }

            line = line.Trim();

            if (line.Length == 0)
            {
                return info;
            }

            if (macros.ContainsKey(" ") && macros[" "] != null)
            {
                if (line[0] == '}')
                {
                    macros[" "] = null;
                }
                else
                {
                    if (macros[" "].Lines == null)
                    {
                        macros[" "].Lines = new List<string>();
                    }
                    macros[" "].Lines.Add(line);
                }

                return info;
            }

            #region [Macros]
            if (line[0] == '#')
            {
                line = line.Substring(1).Trim();
                var dir_end = line.IndexOf(" ");
                if (dir_end < 0)
                {
                    dir_end = line.Length;
                }
                info.Direcrive = line.Substring(0, dir_end).ToLower();
                if (info.Direcrive == "macro")
                {
                    
                    line = line.Substring(dir_end).Trim();
                    if (line.Length == 0 || line[line.Length - 1] != '{')
                    {
                        throw new Exception("Invalid macro definition");
                    }
                    line = line.Substring(0, line.Length - 1).Trim();

                    var name_end = line.IndexOf("(");
                    if (name_end < 0)
                    {
                        name_end = line.Length;
                    }
                    var macro = line.Substring(0, name_end).Trim().ToLower();

                    if (macro.Length == 0)
                    {
                        throw new Exception("Macro name can not be empty");
                    }
                    if (reservedKeywords.IndexOf(macro.ToUpper()) > -1)
                    {
                        throw new Exception("Invalid macro name: reserved keyword");
                    }

                    macros[" "] = new Macro {Lines = new List<string>(), Params = new List<string>()};
                    line = line.Substring(name_end).Trim();

                    if (line.Length > 0)
                    {
                        if (line.Length < 2 || line[0] != '('  || line[line.Length -1] != ')')
                        {
                            new Exception("Invalid macro definition (param list within parentheses expected");
                        }
                        line = line.Substring(1, line.Length - 2);
                        var param_list = line.Split(new char[] {','});
                        foreach (var param in param_list)
                        {
                            macros[" "].Params.Add(param.Trim().ToLower());
                        }
                    }

                    /*
                     *       macros[macro + "(" + macros[" "].params.length + ")"] = macros[" "];
      if (!macros[macro]) macros[macro] = [];
      macros[macro].push(macros[" "].params.length);
      return info;
                     */
                    macros[macro + "(" + macros[" "].Params.Count + ")"] = macros[" "];
                    return info;
                }
                else
                {
                    throw new Exception("Unknown directive");
                }
            }
            #endregion


            if (line[line.Length - 1] == '}' || line[line.Length-1] == '{')
            {
                line = line.Substring(0, line.Length - 1).Trim();
            }

            if (line[0] == ':')
            {
                line = line.Substring(1).Trim();
            }

            var label_end = line.IndexOf(" ");
            if (label_end < 0)
            {
                label_end = line.Length;
            }
            info.Label = line.Substring(0, label_end).ToLower();
            line = line.Substring(label_end).Trim();
            if (info.Label.Length == 0)
            {
                if (labels != null)
                {
                    throw new Exception("WHY?");
                }
                info.Label = null;
            }
            else if (Regex.IsMatch(info.Label, "/^[a-z_.][a-z_.0-9]*$/"))
            {
                if (labels != null)
                {
                    throw new Exception("Invalid label name");
                }
                info.Label = null;
            }

            if (line.Length == 0)
            {
                return info;
            }

            if (line[0] == '.')
            {
                return info;
            }

            var op_end = line.IndexOf(" ");
            var par = line.IndexOf("(");
            if (op_end < 0)
            {
                op_end = line.Length;
            }
            if (par > -1 && par < op_end)
            {
                op_end = par;
            }
            info.Op = line.Substring(0, op_end).ToUpper();
            line = line.Substring(op_end).Trim();
            if (subst.ContainsKey(info.Op.ToLower()))
            {
                info.Op = subst[info.Op.ToLower()];
            }

            var macro_nm = (macros.ContainsKey(info.Op.ToLower()) ?  macros[info.Op.ToLower()] : (Macro)null);
            if (macro_nm != null)
            {
                if (line.Length > 1 && line[0] == '(' && line[line.Length - 1] == ')')
                {
                    line = line.Substring(1, line.Length - 2).Trim();
                }
            }

            var vals = new List<String> { String.Empty};
            in_string = false;
            for (var i = 0; i< line.Length; i++)
            {
                if (line[i] == '\\' && i < line.Length - 1)
                {
                    i++;
                    vals[vals.Count - 1] += line[i];
                }
                else if (line[i] == '"')
                {
                    in_string = !in_string;
                    vals[vals.Count - 1] += line[i];
                }
                else if (line[i] == ',' && ! in_string)
                {
                    vals.Add(String.Empty);
                }
                else if (in_string || line[i] != ' ')
                {
                    vals[vals.Count - 1] += line[i];
                }
            }
     
            if (vals[vals.Count - 1] == "") vals.RemoveAt(vals.Count - 1);
            for (var i = 0; i< vals.Count; i++)
            {
                if (subst.ContainsKey(vals[i].ToLower()))
                {
                    vals[i] = subst[vals[i].ToLower()];
                }
            }
            if (in_string)
            {
                throw new Exception("Expected slash before end of line");
            }

            if (macro_nm != null)
            {
                if (macro_nm.Params.Count != vals.Count)
                {
                    throw new Exception("Wrong number of params for macros");
                }

                var macro = macros[info.Op.ToLower() + "(" + vals.Count + ")"];
                var macro_subst = new Dictionary<String, String>();
                for (var i = 0; i < macro.Params.Count; i++)
                {
                    macro_subst[macro.Params[i]] = vals[i];
                }

                for (var i = 0; i < macro.Lines.Count; i++)
                {
                    var macro_info = CompileLine(index, offset, macro.Lines[i], labels, macros, macro_subst);
                    if (macro_info == null)
                    {
                        throw new Exception();
                    }

                    info.MaxSize += macro_info.MaxSize;
                    info.Size += macro_info.Size;
                    for (var j = 0; j < macro_info.dump.Count; j++)
                    {
                        info.dump.Add(macro_info.dump[j]);
                    }
                }
                return info;
            }

            if (info.Op == "DAT")
            {
                for (var j = 0; j < vals.Count; j++)
                {
                    if (vals[j].Length > 0)
                    {
                        if (vals[j][0] == '"')
                        {
                            if (vals[j].Length < 2 || vals[j][vals[j].Length - 1] != '"')
                            {
                                throw new Exception("Expected quote");
                            }
                            vals[j] = vals[j].Substring(1, vals[j].Length - 2);
                            for (var k = 0; k < vals[j].Length; k++)
                            {
                                info.MaxSize++;
                                info.Size++;
                                info.dump.Add(String.Format("{0}", vals[j]));
                            }
                        }
                        else
                        {
                            var value = ParseExpression(index, offset, vals[j], 0, labels);
                            if (value != null)
                            {
                                value = SimplifyExpression(value);
                            }
                            if (value == null)
                            {
                                throw new Exception();
                            }
                            if (!value.Literal.HasValue && !value.Incomplete)
                            {
                                throw new Exception("Literal expected");
                            }

                            if (value.Literal < 0 || value.Literal > 0xffff)
                            {
                                value.Literal = value.Literal & 0xFFFF;
                            }

                            info.MaxSize++;
                            info.Size++;
                            info.dump.Add(value.Literal.ToString());
                        }
                    }
                }
                return info;
            }

            if (info.Op == "ORG")
            {
                if (vals.Count != 1)
                {
                    throw new Exception("ORG Requires 1 value");
                }

                var value = ParseExpression(index, offset, vals[0], 0, labels);
                if (value != null)
                {
                    value = SimplifyExpression(value);
                }

                if (value == null)
                {
                    throw new Exception();
                }

                if (!value.Literal.HasValue)
                {
                    throw new Exception("Literal expected; labels in ORGs are not yet supported");
                }
                info.Org = value.Literal;
                return info;
            }

            var opIndexLine = basicOperations.IndexOf(info.Op);
            var valueA = new DecodeValueResult() {Code = 0, MaxSize = 0, Complete = true};
            var valueB = new DecodeValueResult() { Code = 0, MaxSize = 0, Complete = true };
            var opcode = opIndexLine + 1;
            if (opIndexLine >= 0)
            {
                if (vals.Count != 2)
                {
                    throw new Exception("Basic isntruction requires 2 values");
                }

                valueA = DecodeValue(index, offset, vals[0], labels);
                valueB = DecodeValue(index, offset, vals[1], labels);

                if (valueA != null && valueB != null && valueA.Literal && !valueB.Pointer && opIndexLine < 11 && labels != null)
                {
                    throw new Exception("Assignment to literal.");
                }
            }
            else
            {
                opIndexLine = nonBasicOperations.IndexOf(info.Op);
                if (opIndexLine == -1)
                {
                    throw new Exception("Unknown instruction");
                }
                if (vals.Count != 1)
                {
                    throw new Exception("Non-basic instruction requires 1 value.");
                }
                opcode = 0;
                valueA = new DecodeValueResult {Code = opIndexLine + 1, MaxSize = 0, Complete = true};
                valueB = DecodeValue(index, offset, vals[0], labels);
            }

            if (valueA == null || valueB == null)
            {
                throw new Exception();
            }

            info.MaxSize = 1 + valueA.MaxSize + valueB.MaxSize;
            if (valueA.Complete && valueB.Complete)
            {
                info.dump.Add((opcode | (valueA.Code << 4) | (valueB.Code << 10)).ToString());
                if (valueA.NextWord != null)
                {
                    info.dump.Add(valueA.NextWord.ToString());
                    
                }
                if (valueB.NextWord != null)
                {
                    info.dump.Add(valueB.NextWord.ToString());
                }
            }

            return info;
        }
    }
}
